﻿using System;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Xml;

using Yawf.Core.Logs;
using Yawf.Core.Exceptions;


namespace Yawf.Core.Xml {
	public class XmlDsigUtils {
		public static String XMLDSIG_NAMESPACE = "http://www.w3.org/2000/09/xmldsig#";

		/// <summary>
		/// This method signs a XML Document
		/// </summary>
		/// <param name="xmlDoc">Document to sign</param>
		/// <param name="certificate">Certificate to sign with</param>
		/// <param name="signUrl">url to sign inside document, if empty the entire document is sign</param>
		/// <param name="data">Xml Document contaning propitary data to include in signature</param>
		/// <returns></returns>
		private static SignedXml ComputeSignature(XmlDocument xmlDoc,X509Certificate2 certificate,String signUrl,XmlDocument data) {
			DataObject dataObject;
			SignedXml signedXML;
			KeyInfo keyInfo;
			Reference rSobre;
			KeyInfoX509Data infoData;

			// OJO OJO OJO
			// se define Signedxmlf  NO SignedXml
			signedXML = new SignedXmlf(xmlDoc);

			if (data != null && data.ChildNodes != null) {
				// Add data object if present
				dataObject = new DataObject();
				dataObject.Data = data.ChildNodes;
				signedXML.AddObject(dataObject);
			}
			//crea una referencia para apuntar al contenido a ser firmado (URI)
			signedXML.SignedInfo.CanonicalizationMethod = SignedXmlf.XmlDsigCanonicalizationUrl;
			if ((signUrl == null) || (signUrl.Trim() == "")) {
				rSobre = new Reference();
				rSobre.Uri = "";
				rSobre.AddTransform(new XmlDsigEnvelopedSignatureTransform());
			} else {
				rSobre = new Reference(string.Format("#{0}",signUrl));
				rSobre.AddTransform(new XmlDsigC14NTransform());
			}


			//Agrega las referencias al SignedXML
			signedXML.AddReference(rSobre);

			// Pablo Pombo R.
			// 03-06-2004
			// Se modifica rutina para que use la llave de firma directamente desde el certificado sin identificar el nombre del contenedor.
			keyInfo = new KeyInfo();
			keyInfo.AddClause(new RSAKeyValue((RSA)certificate.PrivateKey));
			infoData = new KeyInfoX509Data(certificate);
			infoData.AddSubjectName(certificate.Subject);
			keyInfo.AddClause(infoData);


			// Calcula Firma	
			signedXML.KeyInfo = keyInfo;
			signedXML.SigningKey = certificate.PrivateKey;
			signedXML.ComputeSignature();

			return signedXML;
		}						// ComputeSignature

		// <summary>
		// Firma un documento XML
		// </summary>
		// <param name="xmlDoc"></param>
		// <param name="certificado"></param>
		// <param name="idAFirmar"></param>
		public static XmlDocument FirmarDocumento(XmlDocument xmlDoc,X509Certificate2 certificado,String idAFirmar) {
			return FirmarDocumento(xmlDoc,certificado,idAFirmar,null);
		}

		// <summary>
		// Firma un documento XML
		// </summary>
		// <param name="xmlDoc"></param>
		// <param name="certificado"></param>
		// <param name="idAFirmar"></param>
		public static XmlDocument FirmarDocumento(XmlDocument xmlDoc,X509Certificate2 certificado,String idAFirmar,XmlDocument data) {
			SignedXml signedXML;
			XmlElement signedElem;

			signedXML = ComputeSignature(xmlDoc,certificado,idAFirmar,data);
			if (signedXML != null) {
				signedElem = signedXML.GetXml();
				XmlUtils.ImportChild(xmlDoc,signedElem);
				return xmlDoc;
			}

			return null;
		}										// FirmarDocumento

		/// <summary>
		/// Validate an Xml Document against a XML Dsig Signature
		/// </summary>
		/// <param name="xmlDoc">Xml DOcumento with Xml Signature included</param>
		/// <param name="firma">Xml Dsig Signature to validate document with</param>
		/// <returns>True if Signature is valid, false if not</returns>
		public static bool ValidateSignature(XmlDocument xmlDoc,XmlElement firma) {
			SignedXml signer;

			try {
				// Check first that xml document is not empty
				if (xmlDoc == null) {
					throw new Exception("An empty Xml Documento can not be verified");
				}

				// Create SignedXml Object
				signer = new SignedXml(xmlDoc);
				signer.LoadXml(firma);

				// Check Signature
				if (!signer.CheckSignature()) {
					throw new Exception("Firma no es valida");
				}
			} catch (Exception e) {
				Log.ExceptionMessage(typeof(XmlDsigUtils),"Error al validar firma",e);
				return false;
			}

			return true;
		}								// ValidateSignature

		/// <summary>
		/// Validates a List of signatures against an XML DOcument
		/// </summary>
		/// <param name="xmlDoc">Xml documento to validate signatures against</param>
		/// <param name="signatureList">List of signatures</param>
		/// <returns>true if all signatures all valid, false if not. If there are no signatures in list this method returns true</returns>
		public static bool ValidateSignature(XmlDocument xmlDoc,XmlNodeList signatureList) {
			int i;

			try {
				// Check first that xml document is not empty
				if (xmlDoc == null) {
					throw new YawfException("An empty Xml Documento can not be verified");
				}

				// Loop through all Signature Elements and verify them against the document
				for (i = 0; i < signatureList.Count; i++) {
					if (!ValidateSignature(xmlDoc,(XmlElement)signatureList[i])) {
						return false;
					}
				}
			} catch (Exception e) {
				Log.ExceptionMessage(typeof(XmlDsigUtils), "Error al validar firma", e);
				return false;
			}

			return true;
		}									// ValidateSignature

		/// <summary>
		/// Validate
		/// </summary>
		/// <param name="xmlDoc"></param>
		/// <returns></returns>
		public static bool ValidateSignature(XmlDocument xmlDoc) {
			return ValidateSignature(xmlDoc,xmlDoc.GetElementsByTagName("Signature",XMLDSIG_NAMESPACE));
		}

	}								// Class XmlDsigUtils

	/// <summary>
	/// Internal class to use for look up of Id url to sign
	/// </summary>
	internal class SignedXmlf : SignedXml {
		public SignedXmlf(XmlDocument document) : base(document) { }

		public override XmlElement GetIdElement(XmlDocument document,string idValue) {
			XmlElement elem;

			// Si vacio retorna null
			if (document == null) {
				return null;
			}

			// Obtiene elemento por attributo Id
			elem = (XmlElement)document.SelectSingleNode("//*[@Id=\"" + idValue + "\"]");
			if (elem != null) { return elem; }

			// Obtiene elemento por attributo id
			elem = (XmlElement)document.SelectSingleNode("//*[@id=\"" + idValue + "\"]");
			if (elem != null) { return elem; }

			// Obtiene elemento por attributo ID
			elem = (XmlElement)document.SelectSingleNode("//*[@ID=\"" + idValue + "\"]");
			return elem;
		}
	}						// SignedXmlf
}
